package com.example.adminbase.mybatisgenerator;

import com.example.adminbase.dao.CrudDao;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.*;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

public class CustomXmlMapperPlugin extends PluginAdapter {


    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        XmlElement rootElement = document.getRootElement();
        rootElement.addElement(findAllElement(introspectedTable));
        if(introspectedTable.getGeneratedKey() != null && introspectedTable.getGeneratedKey().getColumn() != null) {
            rootElement.addElement(findByIdElement(introspectedTable));
            rootElement.addElement(updateElement(introspectedTable));
            rootElement.addElement(updateSelectiveElement(introspectedTable));
            rootElement.addElement(deleteElement(introspectedTable));
            rootElement.addElement(deleteWithIdsElement(introspectedTable));
        }
        return true;
    }

    private XmlElement findByIdElement(IntrospectedTable introspectedTable) {
        XmlElement select = MybatisGeneratorUtil.buildDeletableElement("select");
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());
        select.addAttribute(new Attribute("id", "findById"));
        select.addAttribute(new Attribute("parameterType", idColumn.getFullyQualifiedJavaType().getFullyQualifiedNameWithoutTypeParameters()));
        select.addAttribute(new Attribute("resultMap", introspectedTable.getBaseResultMapId()));
        select.addElement(new TextElement(
                String.format("select * from %s where %s = #{%s, jdbcType=%s}",
                        introspectedTable.getFullyQualifiedTableNameAtRuntime(),
                        idColumn.getActualColumnName(), idColumn.getJavaProperty(), idColumn.getJdbcTypeName())
        ));
        return select;
    }

    private XmlElement deleteWithIdsElement(IntrospectedTable introspectedTable) {
        XmlElement delete = MybatisGeneratorUtil.buildDeletableElement("delete");
        delete.addAttribute(new Attribute("id", "deleteWithIds"));
        delete.addAttribute(new Attribute("parameterType", "list"));

        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());
        delete.addElement(new TextElement(
                String.format("delete from %s where %s in ", introspectedTable.getFullyQualifiedTableNameAtRuntime(),
                        idColumn.getActualColumnName())
        ));
        XmlElement foreach = new XmlElement("foreach");
        foreach.addAttribute(new Attribute("collection", "list"));
        foreach.addAttribute(new Attribute("item", "item"));
        foreach.addAttribute(new Attribute("separator", ","));
        foreach.addAttribute(new Attribute("open", "("));
        foreach.addAttribute(new Attribute("close", ")"));
        foreach.addElement(new TextElement(
                String.format("#{item, jdbcType=%s}", idColumn.getJdbcTypeName())
        ));
        delete.addElement(foreach);
        return delete;
    }

    private Method deleteWithIdsMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method deleteWithIds = MybatisGeneratorUtil.buildDeletableMethod("deleteWithIds");
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());
        FullyQualifiedJavaType list = new FullyQualifiedJavaType("java.util.List<" +
                idColumn.getFullyQualifiedJavaType().getFullyQualifiedName() + ">");

        Parameter ids = new Parameter(list, "ids");
        ids.addAnnotation("@Param(\"list\")");
        deleteWithIds.addParameter(ids);
        deleteWithIds.setReturnType(new FullyQualifiedJavaType("int"));
        deleteWithIds.setVisibility(JavaVisibility.PUBLIC);

        interfaze.addImportedType(new FullyQualifiedJavaType(list.getFullyQualifiedNameWithoutTypeParameters()));
        interfaze.addImportedType(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param"));
        interfaze.addImportedTypes(new HashSet<>(list.getTypeArguments()));
        return deleteWithIds;
    }

    private XmlElement deleteElement(IntrospectedTable introspectedTable) {
        XmlElement delete = MybatisGeneratorUtil.buildDeletableElement("delete");
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());
        delete.addAttribute(new Attribute("id", "delete"));
        delete.addAttribute(new Attribute("parameterType", idColumn.getFullyQualifiedJavaType().getFullyQualifiedNameWithoutTypeParameters()));

        delete.addElement(new TextElement(String.format(
                "delete from %s where %s = #{%s, jdbcType=%s}", introspectedTable.getFullyQualifiedTableNameAtRuntime(),
                idColumn.getActualColumnName(), idColumn.getJavaProperty(), idColumn.getJdbcTypeName())));
        return delete;
    }
    private XmlElement updateSelectiveElement(IntrospectedTable introspectedTable) {
        XmlElement updateSelectiveElement = MybatisGeneratorUtil.buildDeletableElement("update");
        updateSelectiveElement.addAttribute(new Attribute("id", "updateSelective"));
        updateSelectiveElement.addAttribute(new Attribute("parameterType", introspectedTable.getBaseRecordType()));

        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());

        updateSelectiveElement.addElement(new TextElement("update " + introspectedTable.getFullyQualifiedTableNameAtRuntime()));

        XmlElement trim = new XmlElement("trim");
        trim.addAttribute(new Attribute("prefix", "set "));
        trim.addAttribute(new Attribute("suffixOverrides", ","));

        for (IntrospectedColumn column : introspectedTable.getAllColumns()) {
            if(!column.equals(idColumn)) { //id列不更新
                trim.addElement(notNullIfElement(column.getJavaProperty(),
                        new TextElement(String.format(
                                "%s = #{%s, jdbcType=%s},", column.getActualColumnName(), column.getJavaProperty(), column.getJdbcTypeName()))));
            }
        }
        updateSelectiveElement.addElement(trim);
        updateSelectiveElement.addElement(new TextElement(
                String.format("where %s = #{%s, jdbcType=%s}", idColumn.getActualColumnName(), idColumn.getJavaProperty(), idColumn.getJdbcTypeName())
        ));
        return updateSelectiveElement;
    }


    private XmlElement notNullIfElement(String javaProperty, Element childElement) {
        XmlElement ifElement = new XmlElement("if");
        ifElement.addAttribute(new Attribute("test", String.format("%s != null", javaProperty)));
        ifElement.addElement(childElement);
        return ifElement;
    }

    private XmlElement updateElement(IntrospectedTable introspectedTable) {
        XmlElement updateElement = MybatisGeneratorUtil.buildDeletableElement("update");
        updateElement.addAttribute(new Attribute("id", "update"));
        updateElement.addAttribute(new Attribute("parameterType", introspectedTable.getBaseRecordType()));
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());
        updateElement.addElement(new TextElement("update " + introspectedTable.getFullyQualifiedTableNameAtRuntime() + " set "));
        StringBuilder stringBuilder = new StringBuilder();
        for (IntrospectedColumn column : introspectedTable.getAllColumns()) {
            if(!column.equals(idColumn)) {
                stringBuilder.append(String.format("%s = #{%s, jdbcType=%s}", column.getActualColumnName(), column.getJavaProperty(), column.getJdbcTypeName()));
                stringBuilder.append(",");
            }
        }
        updateElement.addElement(new TextElement(stringBuilder.substring(0, stringBuilder.length() - 1)));
        updateElement.addElement(new TextElement(String.format("where %s = #{%s, jdbcType=%s}", idColumn.getActualColumnName(),
                idColumn.getJavaProperty(), idColumn.getJdbcTypeName())));
        return updateElement;
    }

    private XmlElement findAllElement(IntrospectedTable introspectedTable) {
        XmlElement selectAllElement = MybatisGeneratorUtil.buildDeletableElement("select");

        selectAllElement.addAttribute(new Attribute("id", "findAll"));
        selectAllElement.addAttribute(new Attribute("resultMap", introspectedTable.getBaseResultMapId()));
        selectAllElement.addElement(new TextElement("select * from " + introspectedTable.getTableConfiguration().getTableName()));
        return selectAllElement;
    }

    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        interfaze.addMethod(findAllMethod(interfaze, introspectedTable));
        if(introspectedTable.getGeneratedKey() != null && introspectedTable.getGeneratedKey().getColumn() != null) {
            interfaze.addMethod(findByIdMethod(interfaze, introspectedTable));
            interfaze.addMethod(updateMethod(interfaze, introspectedTable));
            interfaze.addMethod(updateSelectiveMethod(interfaze, introspectedTable));
            interfaze.addMethod(deleteMethod(interfaze, introspectedTable));
            interfaze.addMethod(deleteWithIdsMethod(interfaze, introspectedTable));
        }

        //todo: 抽取到新的Plugin类中
        IntrospectedColumn idColumn = introspectedTable.getGeneratedKey() != null ?
                introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn()) : null;
        for (FullyQualifiedJavaType superInterfaceType : interfaze.getSuperInterfaceTypes()) {
            if(CrudDao.class.getName().equals(superInterfaceType.getFullyQualifiedNameWithoutTypeParameters())
                    && idColumn != null) {
                //继承的类中已包含基本的增、删、改、查两个方法
                interfaze.getMethods().removeIf(method -> Arrays.asList("findAll", "findById",
                        "update","updateSelective",
                        "insert", "insertSelective",
                        "delete", "deleteWithIds").contains(method.getName()));

                superInterfaceType.addTypeArgument(idColumn.getFullyQualifiedJavaType());
                superInterfaceType.addTypeArgument(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()));
                interfaze.addImportedType(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()));
            }
        }
        return true;
    }
    private Method findByIdMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method method = MybatisGeneratorUtil.buildDeletableMethod("findById");
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());

        method.addParameter(new Parameter(idColumn.getFullyQualifiedJavaType(), "id"));
        method.setReturnType(new FullyQualifiedJavaType("int"));
        method.setVisibility(JavaVisibility.PUBLIC);
        return method;
    }

    private Method updateSelectiveMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method updateSelective = MybatisGeneratorUtil.buildDeletableMethod("updateSelective");
        updateSelective.addParameter(new Parameter(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()), "record"));
        updateSelective.setReturnType(new FullyQualifiedJavaType("int"));
        updateSelective.setVisibility(JavaVisibility.PUBLIC);
        return updateSelective;
    }

    private Method deleteMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method delete = MybatisGeneratorUtil.buildDeletableMethod("delete");
        IntrospectedColumn idColumn = introspectedTable.getColumn(introspectedTable.getGeneratedKey().getColumn());

        delete.addParameter(new Parameter(idColumn.getFullyQualifiedJavaType(), "id"));
        delete.setReturnType(new FullyQualifiedJavaType("int"));
        delete.setVisibility(JavaVisibility.PUBLIC);
        return delete;
    }

    private Method findAllMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method method = MybatisGeneratorUtil.buildDeletableMethod("findAll");

        FullyQualifiedJavaType fullyQualifiedJavaType = new FullyQualifiedJavaType("java.util.List<"+introspectedTable.getBaseRecordType()+">");
        interfaze.getImportedTypes().add(new FullyQualifiedJavaType(fullyQualifiedJavaType.getFullyQualifiedNameWithoutTypeParameters()));
        interfaze.getImportedTypes().addAll(fullyQualifiedJavaType.getTypeArguments());

        method.setReturnType(fullyQualifiedJavaType);
        method.setVisibility(JavaVisibility.PUBLIC);

        return method;
    }

    private Method updateMethod(Interface interfaze, IntrospectedTable introspectedTable) {
        Method update = MybatisGeneratorUtil.buildDeletableMethod("update");
        update.addParameter(new Parameter(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()), "record"));
        update.setReturnType(new FullyQualifiedJavaType("int"));
        update.setVisibility(JavaVisibility.PUBLIC);
        return update;
    }


}
